2017/08/17

Recent entries from same category

  1. Go 言語プログラミングエッセンスという本を書きました。
  2. errors.Join が入った。
  3. unsafe.StringData、unsafe.String、unsafe.SliceData が入った。
  4. Re: Go言語で画像ファイルか確認してみる
  5. net/url に JoinPath が入った。

2020/07/20 追記: 最近の Windows 10 ではこの動作が変更されている様です。

https://play.golang.org/p/CHGYhtzRsK Go 1.8.3で『GOOS="darwin"』だと完走するけど、『GOOS=...

Go 1.8.3で『GOOS="darwin"』だと完走するけど、『GOOS="windows"』だと途中でエラーになるプログラム。


osやsyscallパッケージを使っている時、こういう事があって当然なのかパッケージの実装に問題があるのか判断する規準となりそうな記述って公式になにかありますでしょうか。

https://plus.google.com/u/0/103737163485109218200/posts/gfNS2Bz4QrH?cfem=1

UNIX だと以下のコードはパスするのに Windows だとエラーになるという話。

package main

import (
    "fmt"
    "os"
)

func chk(err error) {
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func main() {
    f, err := os.Create("log.txt")
    chk(err)
    defer f.Close()
    err = os.Rename("log.txt""log.txt.bak")
    chk(err)

    os.Remove("log.txt")
    os.Remove("log.txt.bak")
}

UNIX の場合、ファイルを開いている最中にファイルを削除するとファイルシステム上からは unlink されるけど、実際は存在していてファイルの読み込みが出来ます。そして close(2) されたタイミングで削除されます。Windows の FILE_SHARE_DELETE は一見、UNIX のそれっぽく見えるのですが動作が異なります。

例えば test.txt というファイルを作り以下のコードを実行します。

#include <windows.h>
#include <stdio.h>

int
main(int argc, char* argv[]) {
  HANDLE h = CreateFile("test.txt",
      GENERIC_READ | GENERIC_WRITE,
      FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
      NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  getchar();
  char buf[256] = {0};
  DWORD nread;
  printf("%d\n", ReadFile(h, buf, 256, &nread, NULL));
  printf("%s,%d\n", buf, nread);
  return 0;
}

getchar() で処理が停止している間に別のコマンドプロンプトでファイルを消してみて下さい。まずは実行

delete1

そして削除。ファイルが消えると思いきや残っています。そしてアクセスするとエラーになります。

delete2

Windows の FILE_SHARE_DELETE の動作は UNIX のそれとは異なるのです。例えばファイルを消した後で、ファイルが存在しないつもりで処理を続行してしまうと別のエラーが発生する事になります。

package main

import (
    "fmt"
    "os"
)

func chk(err error) {
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func main() {
    f, err := os.Create("log.txt")
    chk(err)
    defer f.Close()
    f.Close()
    err = os.Rename("log.txt""log.txt.bak")
    chk(err)

    os.Remove("log.txt")
    os.Remove("log.txt.bak")
}

この様な OS 間の動作の違いを吸収する事は言語レベルでは出来ません。マルチプラットフォームで動作するコードを書きたいと思われるのであれば、os.Rename や os.Remove の前に Close する事をおすすめします。

ちなみに「みんなのGo言語」にも解説が書いてあります。

Posted at by